<?php
$this->params['breadcrumbs'][] = '';
?>


<?php
$md = <<<MD
# hello
## hello
### hello
#### hello
##### hello
###### hello
```php
\$name = 'chenzhiwei';
echo 'chenzhiwei';

```
> hahah
MD;
$Parsedown = new Parsedown();

echo $Parsedown->text($md);
echo \yii\helpers\Markdown::process($md);
echo \yii\helpers\Markdown::process($md, 'extra');
echo \yii\helpers\Markdown::process($md, 'gfm');
//$markdown = new \cebe\markdown\GithubMarkdown();
//$markdown->html5 = true;
//echo $markdown->parse($md);

?>


<h2>Yii2 Login机制详解</h2>
<h3>1、背景</h3>
<p>Yii2提供了默认的登录机制，今天我们来详细解读一下源码，看一下它是怎么工作的。</p>
<p>&nbsp;</p>
<h3>2、登录路由</h3>
<h4>2.1 登录页面</h4>
<p>我们通过url可以找到登录的控制器入口，url如下：</p>
<pre><code class='language-shell' lang='shell'>http://172.22.0.3/index.php?r=site%2Flogin
</code></pre>
<p>因此入口应该是<code>backend/controllers/SiteController.php</code>的<code>actionLogin()</code>方法：</p>
<pre><code class='language-php' lang='php'>    public function actionLogin()
    {
        if (!Yii::$app-&gt;user-&gt;isGuest) {
            return $this-&gt;goHome();
        }

        $this-&gt;layout = &#39;blank&#39;;

        $model = new LoginForm();
        if ($model-&gt;load(Yii::$app-&gt;request-&gt;post()) &amp;&amp; $model-&gt;login()) {
            return $this-&gt;goBack();
        } else {
            $model-&gt;password = &#39;&#39;;

            return $this-&gt;render(&#39;login&#39;, [
                &#39;model&#39; =&gt; $model,
            ]);
        }
    }
</code></pre>
<p>通过访问这个方法，用<code>$this-&gt;render</code>实现MVC模式，渲染页面，从而浏览器展示登录页面，从代码中可以看到，view页面所在位置是：<code>backend/views/site/login.php</code>。</p>
<h4>2.2 登录请求</h4>
<p>在登录页面填好账号密码后，点击登录按钮，请求到哪里去呢？从页面的元素中我们可以看到，其实就是把请求投递到当前页面所在的控制器，因为<code>action</code>的值和当前页面的<code>url</code>是一致的：</p>
<pre><code class='language-html' lang='html'>&lt;form id=&quot;login-form&quot; action=&quot;/index.php?r=site%2Flogin&quot; method=&quot;post&quot;&gt;
</code></pre>
<h3>3、LoginForm</h3>
<p><code>common/models/LoginForm.php</code>是对登录验证的主要模型，这个模型继承于<code>yii\base\Model</code>，因此可以使用Yii提供的数据验证机制来实现数据有效性验证。</p>
<h4>3.1 load()</h4>
<p><code>$model-&gt;load(Yii::$app-&gt;request-&gt;post())</code>这个方法会把请求过来的数据填充到<code>LoginForm-&gt;attributes</code>中，也就是对类属性赋值，其实<code>load($data, $formName = null)</code>还有第二个参数，用于指定<code>$formName</code>，为啥要这个属性呢？因为post过来的数据有多个层次，如下：</p>
<pre><code class='language-php' lang='php'>array(3) {
  [&quot;_csrf-backend&quot;]=&gt;
  string(88) &quot;BGsAOWgj1SMXlli3QpPFaZClyaac9_9gErgS9Bk_Vw88DWtsOGyWG3LkHM0l9qscvcT45d6dpRVFiV2SWn0iSg==&quot;
  [&quot;LoginForm&quot;]=&gt;
  array(3) {
    [&quot;username&quot;]=&gt;
    string(2) &quot;ad&quot;
    [&quot;password&quot;]=&gt;
    string(6) &quot;123456&quot;
    [&quot;rememberMe&quot;]=&gt;
    string(1) &quot;0&quot;
  }
  [&quot;login-button&quot;]=&gt;
  string(0) &quot;&quot;
}
</code></pre>
<p>yii需要知道<code>$formName = &quot;LoginForm&quot;</code>才能从post中的数据获取属性值。但是上述代码中并没有指定<code>$formName</code>，从源代码可知，如果<code>$formName</code>不设置，<mark>会取当前类名称作为它的值。</mark></p>
<h4>3.2 login()</h4>
<pre><code class='language-php' lang='php'>&lt;?php
namespace common\models;
... ...
class LoginForm extends Model
{
	public function login()
    {
        if ($this-&gt;validate()) {
            return Yii::$app-&gt;user-&gt;login($this-&gt;getUser(), $this-&gt;rememberMe ? 3600 * 24 * 30 : 0);
        }

        return false;
    }
	... ...
}
</code></pre>
<p>此处正是验证用户名和密码的核心代码，由于<code>LoginForm()</code>继承于<code>yii\base\Model</code>，因此可以使用验证器，代码通过<code>$this-&gt;validate()</code>进行验证数据有效性，验证规则如下：</p>
<pre><code class='language-php' lang='php'>&lt;?php
namespace common\models;
... ...
class LoginForm extends Model
{
	public function rules()
    {
        return [
            // username and password are both required
            [[&#39;username&#39;, &#39;password&#39;], &#39;required&#39;],
            // rememberMe must be a boolean value
            [&#39;rememberMe&#39;, &#39;boolean&#39;],
            // password is validated by validatePassword()
            [&#39;password&#39;, &#39;validatePassword&#39;],
        ];
    }
    ... ...
}
</code></pre>
<p>通过上述规则可以实现如下功能：</p>
<p><code>username</code>和<code>password</code>是必须要有的属性，就是说点击登录按钮后必须要传进来的参数，否则的话会报如下错误：</p>
<p><img src=".assets/img/image-20210710161130446.png" referrerpolicy="no-referrer" alt="image-20210710161130446"></p>
<p>而<code>password</code>还有一条验证规则，就是通过<code>validatePassword</code>方法进行验证：</p>
<pre><code class='language-php' lang='php'>&lt;?php
namespace common\models;
... ...
class LoginForm extends Model
{
 	public function validatePassword($attribute, $params)
    {
        if (!$this-&gt;hasErrors()) {
            $user = $this-&gt;getUser();
            if (!$user || !$user-&gt;validatePassword($this-&gt;password)) {
                $this-&gt;addError($attribute, &#39;Incorrect username or password.&#39;);
            }
        }
    }
	... ...
}
</code></pre>
<p>从代码可知，如果前面的验证规则都通过了才会进入密码正确性验证，系统会通过<code>$this-&gt;getUser()</code>这个方法以<code>username</code>为条件搜索用户表，并返回<code>User</code>模型实例：</p>
<pre><code class='language-php' lang='php'>&lt;?php
namespace common\models;
... ...
class LoginForm extends Model
{
	protected function getUser()
    {
        if ($this-&gt;_user === null) {
            $this-&gt;_user = User::findByUsername($this-&gt;username);
        }

        return $this-&gt;_user;
    }
    ... ...
}
</code></pre>
<p>有了<code>User</code>模型实例，就可以通过<code>$user-&gt;validatePassword($this-&gt;password)</code>来对密码进行验证，验证通过后，会进入<code>Yii::$app-&gt;user-&gt;login()</code>，将当前的认证信息切换到当前的用户，注意：</p>
<pre><code class='language-php' lang='php'>Yii::$app-&gt;user-&gt;login($this-&gt;getUser(), $this-&gt;rememberMe ? 3600 * 24 * 30 : 0);
</code></pre>
<p>此处<code>$this-&gt;rememberMe</code>的作用就是维持用户的登录态：</p>
<ul>
    <li>0：浏览器关闭后或者手动清除session后，登录态失效；</li>
    <li>1：登录态会保持到<code>3600 * 24 * 30</code>，即：30天后失效。</li>

</ul>
<p>以后我们会有专门的章节对登录态的机制进行详细的讲解。</p>
<h4>3.3 goBack()</h4>
<p>登录成功后，会回到请求发起的页面重新请求一次，由于此时已经保存了用户认证信息，<code>Yii::$app-&gt;user-&gt;isGuest</code>已经为非，因此会跳转到<code>$this-&gt;goHome()</code>，进入系统<code>Home</code>目录；</p>
<h4>3.4 登录失败</h4>
<p>如果登录失败，则会<code>$model-&gt;password = &#39;&#39;;</code>清除密码，返回到系统登录页面，提示如下信息，要求重新输入密码：</p>
<p><img src=".assets/img/image-20210710162456377.png" referrerpolicy="no-referrer" alt="image-20210710162456377"></p>
<h3>4 未完待续</h3>
<p>关于系统是如何渲染出html元素，如何对用户输入的数据进行反应，报错信息如何动态展示，这些是都是小部件<code>ActiveForm</code>的封装，它的行为取决于我们在Model类中对属性的约束，由于<code>ActiveForm</code>比较复杂，我们会在小部件篇专门介绍，本文主要讲解登录机制，因此不作深入探索。</p>
